package duplicatedataStorage;

import java.io.Serializable;
import java.rmi.AccessException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import rebalance.RebalanceChecks;
import remote.RemoteDistributedHashMap;

import log.Logger;
import messaging.HashTableMessage;
import messaging.InterNodeMessage;
import node.NodeManager;

/**
 * There is an instance of this class for all actual nodes.This class will store a hash map
 * of all the data hash maps for all the nodes of its successor set.
 * @author Team4
 * @version Spring 2013
 *
 */
public class DuplicateStores
{
  /**
   * wait time to send message to the rebalanced node.
   */
  private int WAIT_MESSAGE = 1000;
  /**
   * Put key.
   */
  private int PUT_KEY = 1;
  /**
   * remove key.
   */
  private int REMOVE_KEY = 3;
  /**
   * NodeManager reference.
   */
  private NodeManager my_nodemanager;
  /**
   * Hash Map of all hash maps of all nodes of its successor.
   */
  public HashMap<Integer, HashMap<String, Serializable>> storage = 
      new HashMap<Integer,HashMap<String, Serializable>>();
  
  /**
   * ArrayList of nodes to store copies of.
   */
  public ArrayList<Integer> successor_node_set = new ArrayList<Integer>();;
  /**
   * Multiple store key in the internode message.
   */
  private int MULTIPLESTORE = 8;
  /**
   * predecessor actual node.
   */
  private int predecessor_actual;
  /**
   * my actual node number.
   */
  private int my_actual_id;
  /**
   * Constructor.
   */
  public DuplicateStores(final NodeManager nm, int my_actual)
  {
    this.my_nodemanager = nm;
    this.my_actual_id = my_actual;
    successor_node_set = this.findsuccesornodes();
    for (int i = 0; i < successor_node_set.size(); i++)
    {
      storage.put(successor_node_set.get(i), new HashMap<String, Serializable>());
    }
    this.predecessor_actual = this.findPredecessor(my_actual);
    Logger.out(this.my_actual_id + " The predecessor is " + this.predecessor_actual);
  }
  /**
   * find the predecessor actual node  of this node.
   * @param node_num - the node number for which the predecessor actual node is found.
   * @return int - the predecessor actual node for this node number.
   */
  public int findPredecessor(final int node_num) 
  {
    int index = node_num - 1;
    if (index == NodeManager.total_nodes_chord)
    {
      index = 0;
    }
    if (index < 0)
    {
      index = NodeManager.total_nodes_chord - 1;
    }
    while (my_nodemanager.rmi_NodeFind(index).endsWith("V"))
    {
      index--;
      if (index == NodeManager.total_nodes_chord)
      {
        index = 0;
      }
      if (index < 0)
      {
        index = NodeManager.total_nodes_chord - 1;
      }
    }
    return index;
  }
  /**
   * Sending a message to predecessor for fault tolerance.
   */
  public void sendMessagePredecessor(String key, 
                                     Serializable value,
                                     int hashValue, 
                                     int methodName,
                                     int my_node)
  {
  //checks the existence of the predecessor before sending
  // if predecessor not exist then re-calculate the predecessor.
    String node_name = this.my_nodemanager.rmi_NodeFind(this.predecessor_actual);
    if (node_name.endsWith("V"))
    {
      Logger.out(" The predecessor node " + this.predecessor_actual +" is now virtual.");
      //method to re-calculate predecessor and update.
      refindpredecessor();
    }
    final int dest = 
        this.my_nodemanager.hashhelp.optimalNodeFind(my_node, this.predecessor_actual);
    InterNodeMessage my_message = 
        new InterNodeMessage(my_node, this.predecessor_actual, my_node, dest, MULTIPLESTORE);
    HashTableMessage htm = new HashTableMessage(key, value, hashValue);
    my_message.setHashTableMessage(htm);
    Logger.out("Node - " + my_node + " sending message -  " +
                my_message.getHashTableMessage().getkey() + 
                " value - " + value + " to predecessor - " +
                predecessor_actual + " for fault tolerance storage.");
    final String dest_str = this.my_nodemanager.rmi_NodeFind(dest);
    try
    {
      final RemoteDistributedHashMap to_send =
        (RemoteDistributedHashMap) NodeManager.registry.lookup(dest_str);
      while (to_send.isrebalancing())
      {
        //Logger.out(" rebalancing");
      }
      to_send.send(my_message);
    }
    catch (final RemoteException e)
    {
      final RebalanceChecks reb = 
          new RebalanceChecks(this.my_nodemanager, this.my_actual_id, dest);
      int predecessor = reb.findPredecessor(my_message.getMy_dest_node());
      if (this.my_nodemanager.rmi_NodeFind(my_message.getMy_dest_node()).endsWith("V"))
      {
        predecessor = reb.findPredecessor(predecessor);
      }
      //Logger.out(" the pred is " + predecessor);
      reb.doallchecks();
      try
      {
        this.handlemessage(my_message, predecessor);
      }
      catch ( RemoteException | NotBoundException | InterruptedException e1)
      {
        // TODO Auto-generated catch block
        e1.printStackTrace();
      }
    }
    catch (NotBoundException e)
    {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    catch (InterruptedException e)
    {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
  /**
   * This method handles the message that caused to throw re-balancing.
   * @throws NotBoundException 
   * @throws RemoteException 
   * @throws AccessException 
   * @throws InterruptedException 
   */
  public void handlemessage(InterNodeMessage message ,int predecessor) 
      throws AccessException, RemoteException, NotBoundException, InterruptedException
  {
    
    final long time = System.currentTimeMillis();
    long newtime = System.currentTimeMillis();
    while (newtime - time < WAIT_MESSAGE)
    {
      newtime = System.currentTimeMillis();
    }
    String dest_node_str = this.my_nodemanager.rmi_NodeFind(predecessor);
    RemoteDistributedHashMap to_send =
        (RemoteDistributedHashMap) NodeManager.registry.lookup(dest_node_str);
    while (to_send.isrebalancing())
    {
      //Logger.out(" rebalancing");
    }
    dest_node_str = this.my_nodemanager.rmi_NodeFind(message.getMy_dest_node());
    to_send = (RemoteDistributedHashMap) NodeManager.registry.lookup(dest_node_str);
    Logger.out(this.my_actual_id + " Sending message- " +
          message.getHashTableMessage().getkey() + " to node- " + dest_node_str);
    to_send.send(message);
  }

  /**
   * landing point for a message to make a store into the additional stores.
   * @param my_key
   * @param my_value
   * @param methodid
   * @param my_node
   */
  public void sendmessagepredecessor(final InterNodeMessage message, final int my_node)
  {
    this.sendMessagePredecessor(message.getHashTableMessage().getkey(),
                                message.getHashTableMessage().getvalue(),
                                message.getHashTableMessage().gethash(),
                                message.getMethod_name(),
                                my_node);
  }
  /**
   * This  method receives message from the successor and stores it in the 
   * corresponding hash map.
   * @param InterNodeMessage
   */
  public void receivemessagesuccessor(final InterNodeMessage mymessage)
  {
    Logger.out(this.my_actual_id + " Message- " + mymessage.getHashTableMessage().getkey() +
               " received from successor node " +
       mymessage.getMy_originalsource_node() + " to be stored for fault tolerance.");
       
    final int node_id = mymessage.getHashTableMessage().gethash();
    if (mymessage.getMethod_name() == MULTIPLESTORE)
    {
      if (!storage.containsKey(node_id))
      {
        
      }
      else
      {
        storage.get(node_id).put
        (mymessage.getHashTableMessage().getkey(), mymessage.getHashTableMessage().getvalue());
      Logger.out(this.my_actual_id + " Message - " + mymessage.getHashTableMessage().getkey() +
                 " value - " + mymessage.getHashTableMessage().getvalue() + 
                 " added to storage.");
      }
      
    }
  }
  /**
   * Returns the hash maps that for the given node number.
   */
  public HashMap<String, Serializable> getNodeHashMap(int nodenum)
  {
    return this.storage.get(nodenum);
  }
  /**
   * This method checks if the predecessor actual node does exist. If not it will 
   * re-calculate the new predecessor and update this class.
   */
  public void refindpredecessor()
  {
    int new_predecessor = this.findPredecessor(this.my_actual_id);
    this.predecessor_actual = new_predecessor;
  }
  /**
   * This method will re-initialise the storage and successor node set to accept 
   * the new successor.
   */
  public void initializeStorage()
  {
    storage = new HashMap<Integer,HashMap<String, Serializable>>();
    successor_node_set = new ArrayList<Integer>();
  }
  /**
   * This method finds all the successor nodes from which this node manager
   * will receive storage calls.
   */
  private ArrayList<Integer> findsuccesornodes() 
  {
    final ArrayList<Integer> list_to_return = new ArrayList<Integer>();
    int index = this.my_actual_id + 1;
    if (index == NodeManager.total_nodes_chord)
    {
      index = 0;
    }
    while (this.my_nodemanager.rmi_NodeFind(index).endsWith("V"))
    {
      index++;
      if (index == NodeManager.total_nodes_chord)
      {
        index = 0;
      }
      continue;
    }
    list_to_return.add(index);
    Logger.out("Successor node - " + index);
    index++;
    if (index == NodeManager.total_nodes_chord)
    {
      index = 0;
    }
    while (this.my_nodemanager.rmi_NodeFind(index).endsWith("V"))
    {
      list_to_return.add(index);
      Logger.out("Successor node - " + index);
      index++;
      if (index == NodeManager.total_nodes_chord)
      {
        index = 0;
      }
    }
    return list_to_return;
  }
}
